//---------------------------------------------------------------------
// File: Context.cs
// 
// Summary: 
//
//---------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
// KIND, WHETHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
// PURPOSE.
//---------------------------------------------------------------------

namespace Microsoft.Services.BizTalkApplicationFramework.BizUnit
{
    using System;
    using System.IO;
    using System.Xml;
    using System.Collections;

    /// <summary>
    /// Represents a state object that is passed between BizUnit test steps.
    /// </summary>
    /// 
    /// <remarks>
    /// The context is passed by BizUnit to each individual test step, state maybe written to or read from the context, note the following
    /// wild cards are supported: %DateTime%, %ServerName% and %Guid%.
    /// The context also provides helper methods for test steps to read their configuration, and to log information, warnings, errors 
    /// and data in a consistent manner.
    ///	</remarks>
    public class Context
    {
        private Hashtable context;
        private BizUnit bizUnit;
        private Logger logger;
        private DateTime startTime;
        private const string DATE_TIME = "%DateTime%";
        private const string SERVER_NAME = "%ServerName%";
        private const string GUID = "%Guid%";

        internal enum LogLevel { INFO, WARNING, ERROR };

        /// <summary>
        /// Default Context constructor.
        /// </summary>
        /// 
        /// <remarks>
        /// This may be used for scenarios where by the context object needs 
        /// to be created and passed to BizUnit.
        ///	</remarks>
        public Context()
        {
            this.context = new Hashtable();
            this.startTime = DateTime.Now;
            this.logger = new Logger();
        }

        /// <summary>
        /// Context constructor.
        /// </summary>
        /// <param name="logWritter">Custom implementation of ILogWritter, used 
        /// to redirect the BizUnit test output.</param>
        /// 
        /// <remarks>
        /// This may be used for scenarios where by the context object needs 
        /// to be created and passed to BizUnit, and a custom log writter is 
        /// requred to direct the BizUnit output to a custom sink, to do this 
        /// the ILogWritter interface should be implemented.
        ///	</remarks>
        public Context(ILogWritter logWritter)
        {
            ArgumentValidation.CheckForNullReference(logWritter, "logWritter");

            this.context = new Hashtable();
            this.startTime = DateTime.Now;
            this.logger = new Logger(logWritter);
        }

        internal Context(BizUnit bizUnit, Logger logger)
        {
            this.bizUnit = bizUnit;
            this.logger = logger;
            this.context = new Hashtable();
            this.startTime = DateTime.Now;
        }

        private Context(BizUnit bizUnit, Hashtable ctx, Logger logger, DateTime t)
        {
            this.bizUnit = bizUnit;
            this.logger = logger;
            this.context = ctx;
            this.startTime = t;
        }

        internal void Initialize(BizUnit bizunit)
        {
            ArgumentValidation.CheckForNullReference(bizunit, "bizunit");

            this.bizUnit = bizunit;
        }

        internal Context CloneForConcurrentUse(Logger l)
        {
            return new Context(this.bizUnit, this.context, l, this.startTime);
        }

        /// <summary>
        /// Adds a new object to the context.
        /// </summary>
        /// <param name="key">The name of the key for the object added.</param>
        /// <param name="newValue">The object to be added to the context.</param>
        /// 
        /// <remarks>
        /// The following example demonstrates how to add a new item to the context:
        /// 
        /// <code escaped="true">
        /// context.Add("FILE_NAME", fileName);
        ///	</code>
        ///	
        ///	</remarks>
        public void Add(string key, object newValue)
        {
            ArgumentValidation.CheckForNullReference(key, "key");

            Add(key, newValue, false);
        }

        /// <summary>
        /// Adds a new object to the context.
        /// </summary>
        /// <param name="key">The name of the key for the object added.</param>
        /// <param name="newValue">The object to be added to the context.</param>
        /// <param name="updateIfExists">If the object already exists and this flag 
        /// is set to true, its value will be updated, otherwise the method will 
        /// throw an exception.</param>
        /// 
        /// <remarks>
        /// The following example demonstrates how to add a new item to the context:
        /// 
        /// <code escaped="true">
        /// context.Add("FILE_NAME", fileName);
        ///	</code>
        ///	
        ///	</remarks>
        public void Add(string key, object newValue, bool updateIfExists)
        {
            ArgumentValidation.CheckForNullReference(key, "key");

            LogInfo(string.Format("Adding context property: {0}, value: {1}", key, newValue));

            lock (this.context.SyncRoot)
            {
                if (updateIfExists && this.context.ContainsKey(key))
                {
                    LogInfo(string.Format("Adding context property: {0} already existed, property will be updated", key));
                    this.context.Remove(key);
                }
                this.context.Add(key, newValue);
            }
        }

        /// <summary>
        /// Gets a string previously saved on the context.
        /// </summary>
        /// <param name="key">The name of the key for the object to get.</param>
        /// <returns>string value</returns>
        /// 
        /// <remarks>
        /// The following example demonstrates how to get a string value from the context:
        /// 
        /// <code escaped="true">
        /// string fileName = context.GetValue("FILE_NAME");
        ///	</code>
        ///	
        ///	</remarks>
        public string GetValue(string key)
        {
            ArgumentValidation.CheckForNullReference(key, "key");

            lock (this.context.SyncRoot)
            {
                return (string)this.context[key];
            }
        }

        /// <summary>
        /// Gets an object previously saved to the context .
        /// </summary>
        /// <param name="key">The name of the key for the object to get.</param>
        /// <returns>object</returns>
        /// 
        /// <remarks>
        /// The following example demonstrates how to get an object from the context:
        /// 
        /// <code escaped="true">
        /// object fileName = context.GetObject("FILE_NAME");
        ///	</code>
        ///	
        ///	</remarks>
        public object GetObject(string key)
        {
            ArgumentValidation.CheckForNullReference(key, "key");

            lock (this.context.SyncRoot)
            {
                return this.context[key];
            }
        }

        /// <summary>
        /// Executes a validation test step.
        /// </summary>
        /// <param name="data">The stream containing the data to validate.</param>
        /// <param name="validatorConfig">The Xml configuration for the validation test step, this configuration is passed onto the validation step by BizUnit.</param>
        /// 
        /// <remarks>
        /// The following example demonstrates how to execute a validation test step:
        /// 
        /// <code escaped="true">
        /// XmlNode validationConfig = testConfig.SelectSingleNode("ValidationStep");
        /// 
        /// ...
        /// 
        /// MemoryStream response = HttpHelper.SendRequestData(destinationUrl, data, requestTimeout, context);
        /// context.ExecuteValidator( response, validationConfig );
        ///	</code>
        ///	
        ///	</remarks>
        public void ExecuteValidator(Stream data, XmlNode validatorConfig)
        {
            this.bizUnit.ExecuteValidator(data, validatorConfig, this);
        }

        /// <summary>
        /// Executes a context loader test step.
        /// </summary>
        /// <param name="data">The stream containing the data to validate.</param>
        /// <param name="contextConfig">The Xml configuration for the context loader 
        /// test step, this configuration is passed onto the validation step by BizUnit.</param>
        /// 
        /// <remarks>
        /// Context loader steps load data onto the context. The following example demonstrates how to execute a validation test step:
        /// 
        /// <code escaped="true">
        /// XmlNode contextConfig = testConfig.SelectSingleNode("ContextConfig");
        /// 
        /// ...
        /// 
        /// MemoryStream response = HttpHelper.SendRequestData(destinationUrl, data, requestTimeout, context);
        /// context.ExecuteContextLoader( response, contextConfig );
        ///	</code>
        ///	
        ///	</remarks>
        public void ExecuteContextLoader(Stream data, XmlNode contextConfig)
        {
            this.bizUnit.ExecuteContextLoader(data, contextConfig, this);
        }

        private string ReadConfigAsString(XmlNode config, string xPath, bool optional, bool asXml)
        {
            return (string)ReadConfigAsObject(config, xPath, optional, asXml);
        }

        /// <summary>
        /// Used by a test step to read an object, if the object is in the context 
        /// the object will be returned, otherwise the string value from the Xml configuration 
        /// will be returned.
        /// </summary>
        /// <param name="config">The Xml configuration for the test step.</param>
        /// <param name="xPath">The XPath expression used to query for the configuration value.</param>
        /// <param name="optional">True if this is an option field. If false and the XPath expression does not find a value and exception will be thrown.</param>
        /// <returns>string value</returns>
        /// 
        /// <remarks>
        /// The following example demonstrates how to use the method:
        /// 
        /// <code escaped="true">
        ///	public void Execute(XmlNode testConfig, Context context)
        ///	{
        ///		object fooObj = context.ReadConfigAsObject( testConfig, "Foo", true );
        ///	</code>
        ///	
        ///	</remarks>
        public object ReadConfigAsObject(XmlNode config, string xPath, bool optional)
        {
            ArgumentValidation.CheckForNullReference(config, "config");
            ArgumentValidation.CheckForNullReference(xPath, "xPath");
            ArgumentValidation.CheckForNullReference(optional, "optional");

            return ReadConfigAsObject(config, xPath, optional, false);
        }

        /// <summary>
        /// Used by a test step to read an object, if the object is in the context 
        /// the object will be returned, otherwise the string value from the Xml configuration 
        /// will be returned.
        /// </summary>
        /// <param name="config">The Xml configuration for the test step.</param>
        /// <param name="xPath">The XPath expression used to query for the configuration value.</param>
        /// <returns>string value</returns>
        /// 
        /// <remarks>
        /// The following example demonstrates how to use the method:
        /// 
        /// <code escaped="true">
        ///	public void Execute(XmlNode testConfig, Context context)
        ///	{
        ///		object fooObj = context.ReadConfigAsObject( testConfig, "Foo" );
        ///	</code>
        ///	
        ///	</remarks>
        public object ReadConfigAsObject(XmlNode config, string xPath)
        {
            ArgumentValidation.CheckForNullReference(config, "config");
            ArgumentValidation.CheckForNullReference(xPath, "xPath");

            return ReadConfigAsObject(config, xPath, false);
        }

        private object ReadConfigAsObject(XmlNode config, string xPath, bool optional, bool asXml)
        {
            XmlNode node = config.SelectSingleNode(xPath);

            if (null == node && optional)
            {
                return null;
            }
            else if (null != node)
            {
                // Take the value from the ctx?
                XmlNode fromCtx = node.SelectSingleNode("@takeFromCtx");
                if (null != fromCtx)
                {
                    string s = fromCtx.Value;
                    if (0 < s.Length)
                    {
                        lock (this.context.SyncRoot)
                        {
                            return this.context[s];
                        }
                    }
                }

                if (asXml)
                {
                    return SubstituteWildCards(node.InnerXml);
                }
                else
                {
                    return SubstituteWildCards(node.InnerText);
                }
            }
            else
            {
                throw new NullReferenceException(string.Format("The XPath query: {0} did not find a node.", xPath));
            }
        }

        /// <summary>
        /// Used by a test step to read a configuration string value as Xml from it's Xml configuration.
        /// </summary>
        /// <param name="config">The Xml configuration for the test step.</param>
        /// <param name="xPath">The XPath expression used to query for the configuration value.</param>
        /// <returns>string value</returns>
        /// 
        /// <remarks>
        /// The following example demonstrates how to use the method:
        /// 
        /// <code escaped="true">
        ///	public void Execute(XmlNode testConfig, Context context)
        ///	{
        ///		string directory = context.ReadConfigAsXml( testConfig, "Directory" );
        ///	</code>
        ///	
        ///	</remarks>
        public string ReadConfigAsXml(XmlNode config, string xPath)
        {
            ArgumentValidation.CheckForNullReference(config, "config");
            ArgumentValidation.CheckForNullReference(xPath, "xPath");

            return ReadConfigAsString(config, xPath, false, true);
        }

        /// <summary>
        /// Used by a test step to read a configuration string value as Xml from it's Xml configuration.
        /// </summary>
        /// <param name="config">The Xml configuration for the test step.</param>
        /// <param name="xPath">The XPath expression used to query for the configuration value.</param>
        /// <param name="optional">True if this is an option field. If false and the XPath expression does not find a value and exception will be thrown.</param>
        /// <returns>string value</returns>
        /// 
        /// <remarks>
        /// The following example demonstrates how to use the method:
        /// 
        /// <code escaped="true">
        ///	public void Execute(XmlNode testConfig, Context context)
        ///	{
        ///		string directory = context.ReadConfigAsXml( testConfig, "Directory", true );
        ///	</code>
        ///	
        ///	</remarks>
        public string ReadConfigAsXml(XmlNode config, string xPath, bool optional)
        {
            ArgumentValidation.CheckForNullReference(config, "config");
            ArgumentValidation.CheckForNullReference(xPath, "xPath");
            ArgumentValidation.CheckForNullReference(optional, "optional");

            return ReadConfigAsString(config, xPath, optional, true);
        }

        /// <summary>
        /// Used by a test step to read a configuration string value from it's Xml configuration.
        /// </summary>
        /// <param name="config">The Xml configuration for the test step.</param>
        /// <param name="xPath">The XPath expression used to query for the configuration value.</param>
        /// <returns>string value</returns>
        /// 
        /// <remarks>
        /// The following example demonstrates how to use the method:
        /// 
        /// <code escaped="true">
        ///	public void Execute(XmlNode testConfig, Context context)
        ///	{
        ///		string directory = context.ReadConfigAsString( testConfig, "Directory" );
        ///	</code>
        ///	
        ///	</remarks>
        public string ReadConfigAsString(XmlNode config, string xPath)
        {
            ArgumentValidation.CheckForNullReference(config, "config");
            ArgumentValidation.CheckForNullReference(xPath, "xPath");

            return ReadConfigAsString(config, xPath, false, false);
        }

        /// <summary>
        /// Used by a test step to read a configuration string value from it's Xml configuration.
        /// </summary>
        /// <param name="config">The Xml configuration for the test step.</param>
        /// <param name="xPath">The XPath expression used to query for the configuration value.</param>
        /// <param name="optional">True if this is an option field. If false and the XPath expression does not find a value and exception will be thrown.</param>
        /// <returns>string value</returns>
        /// 
        /// <remarks>
        /// The following example demonstrates how to use the method:
        /// 
        /// <code escaped="true">
        ///	public void Execute(XmlNode testConfig, Context context)
        ///	{
        ///		string directory = context.ReadConfigAsString( testConfig, "Directory", true );
        ///	</code>
        ///	
        ///	</remarks>
        public string ReadConfigAsString(XmlNode config, string xPath, bool optional)
        {
            ArgumentValidation.CheckForNullReference(config, "config");
            ArgumentValidation.CheckForNullReference(xPath, "xPath");
            ArgumentValidation.CheckForNullReference(optional, "optional");

            return ReadConfigAsString(config, xPath, optional, false);
        }

        /// <summary>
        /// Used by a test step to read a configuration Int32 value from it's Xml configuration.
        /// </summary>
        /// <param name="config">The Xml configuration for the test step.</param>
        /// <param name="xPath">The XPath expression used to query for the configuration value.</param>
        /// <param name="optional">True if this is an option field. If false and the XPath expression does not find a value and exception will be thrown.</param>
        /// <returns>int value</returns>
        /// 
        /// <remarks>
        /// The following example demonstrates how to use the method:
        /// 
        /// <code escaped="true">
        ///	public void Execute(XmlNode testConfig, Context context)
        ///	{
        ///		int retryCount = context.ReadConfigAsInt32( testConfig, "RetryCount" );
        ///	</code>
        ///	
        ///	</remarks>
        public int ReadConfigAsInt32(XmlNode config, string xPath, bool optional)
        {
            ArgumentValidation.CheckForNullReference(config, "config");
            ArgumentValidation.CheckForNullReference(xPath, "xPath");
            ArgumentValidation.CheckForNullReference(optional, "optional");

            return Convert.ToInt32(ReadConfigAsString(config, xPath, optional, false));
        }

        /// <summary>
        /// Used by a test step to read a configuration Int32 value from it's Xml configuration.
        /// </summary>
        /// <param name="config">The Xml configuration for the test step.</param>
        /// <param name="xPath">The XPath expression used to query for the configuration value.</param>
        /// <returns>int value</returns>
        /// 
        /// <remarks>
        /// The following example demonstrates how to use the method:
        /// 
        /// <code escaped="true">
        ///	public void Execute(XmlNode testConfig, Context context)
        ///	{
        ///		int retryCount = context.ReadConfigAsInt32( testConfig, "RetryCount", true );
        ///	</code>
        ///	
        ///	</remarks>
        public int ReadConfigAsInt32(XmlNode config, string xPath)
        {
            ArgumentValidation.CheckForNullReference(config, "config");
            ArgumentValidation.CheckForNullReference(xPath, "xPath");

            return Convert.ToInt32(ReadConfigAsObject(config, xPath, false, false));
        }

        /// <summary>
        /// Used by a test step to read a configuration double value from it's Xml configuration.
        /// </summary>
        /// <param name="config">The Xml configuration for the test step.</param>
        /// <param name="xPath">The XPath expression used to query for the configuration value.</param>
        /// <param name="optional">True if this is an option field. If false and the XPath expression does not find a value and exception will be thrown.</param>
        /// <returns>double value</returns>
        /// 
        /// <remarks>
        /// The following example demonstrates how to use the method:
        /// 
        /// <code escaped="true">
        ///	public void Execute(XmlNode testConfig, Context context)
        ///	{
        ///		double timeout = context.ReadConfigAsDouble( testConfig, "Timeout" );
        ///	</code>
        ///	
        ///	</remarks>
        public double ReadConfigAsDouble(XmlNode config, string xPath, bool optional)
        {
            ArgumentValidation.CheckForNullReference(config, "config");
            ArgumentValidation.CheckForNullReference(xPath, "xPath");
            ArgumentValidation.CheckForNullReference(optional, "optional");

            return Convert.ToDouble(ReadConfigAsObject(config, xPath, optional, false));
        }

        /// <summary>
        /// Used by a test step to read a configuration double value from it's Xml configuration.
        /// </summary>
        /// <param name="config">The Xml configuration for the test step.</param>
        /// <param name="xPath">The XPath expression used to query for the configuration value.</param>
        /// <returns>double value</returns>
        /// 
        /// <remarks>
        /// The following example demonstrates how to use the method:
        /// 
        /// <code escaped="true">
        ///	public void Execute(XmlNode testConfig, Context context)
        ///	{
        ///		double timeout = context.ReadConfigAsDouble( testConfig, "Timeout", true );
        ///	</code>
        ///	
        ///	</remarks>
        public double ReadConfigAsDouble(XmlNode config, string xPath)
        {
            ArgumentValidation.CheckForNullReference(config, "config");
            ArgumentValidation.CheckForNullReference(xPath, "xPath");

            return Convert.ToDouble(ReadConfigAsObject(config, xPath, false, false));
        }

        /// <summary>
        /// Used by a test step to read a configuration float value from it's Xml configuration.
        /// </summary>
        /// <param name="config">The Xml configuration for the test step.</param>
        /// <param name="xPath">The XPath expression used to query for the configuration value.</param>
        /// <returns>double value</returns>
        /// 
        /// <remarks>
        /// The following example demonstrates how to use the method:
        /// 
        /// <code escaped="true">
        ///	public void Execute(XmlNode testConfig, Context context)
        ///	{
        ///		float timeout = context.ReadConfigAsFloat( testConfig, "Timeout", true );
        ///	</code>
        ///	
        ///	</remarks>
        public float ReadConfigAsFloat(XmlNode config, string xPath)
        {
            ArgumentValidation.CheckForNullReference(config, "config");
            ArgumentValidation.CheckForNullReference(xPath, "xPath");

            return (float)Convert.ToDouble(ReadConfigAsObject(config, xPath, false, false));
        }

        /// <summary>
        /// Used by a test step to read a configuration bool value from it's Xml configuration.
        /// </summary>
        /// <param name="config">The Xml configuration for the test step.</param>
        /// <param name="xPath">The XPath expression used to query for the configuration value.</param>
        /// <param name="optional">True if this is an option field. If false and the XPath expression does not find a value and exception will be thrown.</param>
        /// <returns>bool value</returns>
        /// 
        /// <remarks>
        /// The following example demonstrates how to use the method:
        /// 
        /// <code escaped="true">
        ///	public void Execute(XmlNode testConfig, Context context)
        ///	{
        ///		bool findFirst = context.ReadConfigAsBool( testConfig, "FindFirst", true );
        ///	</code>
        ///	
        ///	</remarks>
        public bool ReadConfigAsBool(XmlNode config, string xPath, bool optional)
        {
            ArgumentValidation.CheckForNullReference(config, "config");
            ArgumentValidation.CheckForNullReference(xPath, "xPath");
            ArgumentValidation.CheckForNullReference(optional, "optional");

            return Convert.ToBoolean(ReadConfigAsObject(config, xPath, optional, false));
        }

        /// <summary>
        /// Used by a test step to read a configuration bool value from it's Xml configuration.
        /// </summary>
        /// <param name="config">The Xml configuration for the test step.</param>
        /// <param name="xPath">The XPath expression used to query for the configuration value.</param>
        /// <returns>bool value</returns>
        /// 
        /// <remarks>
        /// The following example demonstrates how to use the method:
        /// 
        /// <code escaped="true">
        ///	public void Execute(XmlNode testConfig, Context context)
        ///	{
        ///		bool findFirst = context.ReadConfigAsBool( testConfig, "FindFirst", true );
        ///	</code>
        ///	
        ///	</remarks>
        public bool ReadConfigAsBool(XmlNode config, string xPath)
        {
            ArgumentValidation.CheckForNullReference(config, "config");
            ArgumentValidation.CheckForNullReference(xPath, "xPath");

            return Convert.ToBoolean(ReadConfigAsObject(config, xPath, false, false));
        }

        /// <summary>
        /// Used by a test step to read the inner xml of an XmlNode, if the node has the 
        /// BizUnit attribute takeFromCtx set, the value will be fetched form the BizUnit 
        /// context.
        /// </summary>
        /// <param name="node">The XmlNode to fetch the inner xml from.</param>
        /// <returns>string</returns>
        /// 
        /// <remarks>
        /// The following example demonstrates how to use the method:
        /// 
        /// <code escaped="true">
        ///	public void Execute(XmlNode testConfig, Context context)
        ///	{
        ///     XmlNodeList parameters = testConfig.SelectNodes("Parameter");
        ///     
        ///     ....
        ///     
        ///     foreach (XmlNode paramter in parameters)
        ///     {
        ///        string val = context.GetInnerXml( node );
        ///        ...
        ///     }
        ///	</code>
        ///	
        ///	</remarks>
        public string GetInnerXml(XmlNode node)
        {
            ArgumentValidation.CheckForNullReference(node, "node");

            XmlNode fromCtx = node.SelectSingleNode("@takeFromCtx");
            if (null != fromCtx)
            {
                string s = fromCtx.Value;
                if (0 < s.Length)
                {
                    lock (this.context.SyncRoot)
                    {
                        return (string)this.context[s];
                    }
                }
            }

            return node.InnerXml;
        }

        /// <summary>
        /// Used by a test step to log an Exception caught by the test step, this will be logged in the test output.
        /// </summary>
        /// <param name="e">The Exception to be logged by BizUnit.</param>
        /// 
        /// <remarks>
        /// The following example demonstrates how to use the method:
        /// 
        /// <code escaped="true">
        ///	public void Execute(XmlNode testConfig, Context context)
        ///	{
        ///	
        ///		try
        ///		{
        ///			...
        ///		}
        ///		catch(Exception ex)
        ///		{
        ///			context.LogException( ex );
        ///		}
        ///	</code>
        ///	
        ///	</remarks>
        public void LogException(Exception e)
        {
            ArgumentValidation.CheckForNullReference(e, "e");

            this.logger.LogException(e);
        }

        /// <summary>
        /// Used by a test step to log test Data, this will be logged in the test output.
        /// </summary>
        /// <param name="description">The description of what the data being logged is.</param>
        /// <param name="data">The data to log.</param>
        /// 
        /// <remarks>
        /// The following example demonstrates how to use the method:
        /// 
        /// <code escaped="true">
        ///	public void Execute(XmlNode testConfig, Context context)
        ///	{
        ///	
        ///		...
        ///		context.LogData( "HTTP Response:", data );
        ///	</code>
        ///	
        ///	</remarks>
        public void LogData(string description, string data)
        {
            ArgumentValidation.CheckForNullReference(description, "description");
            ArgumentValidation.CheckForNullReference(data, "data");

            this.logger.LogData(description, data);
        }

        /// <summary>
        /// Used by a test step to log test Data, this will be logged in the test output.
        /// </summary>
        /// <param name="description">The description of what the data being logged is.</param>
        /// <param name="data">The stream containing the data to log.</param>
        /// 
        /// <remarks>
        /// The following example demonstrates how to use the method:
        /// 
        /// <code escaped="true">
        ///	public void Execute(XmlNode testConfig, Context context)
        ///	{
        ///	
        ///		...
        ///		context.LogData( "HTTP Response:", data );
        ///	</code>
        ///	
        ///	</remarks>
        public void LogData(string description, Stream data)
        {
            ArgumentValidation.CheckForNullReference(description, "description");
            ArgumentValidation.CheckForNullReference(data, "data");

            StreamReader sr = new StreamReader(data);
            LogData(description, sr.ReadToEnd());
        }

        /// <summary>
        /// Used by a test step to log test information, this will be logged in the test output.
        /// </summary>
        /// <param name="text">The text to be written to the output.</param>
        /// 
        /// <remarks>
        /// The following example demonstrates how to use the method:
        /// 
        /// <code escaped="true">
        ///	public void Execute(XmlNode testConfig, Context context)
        ///	{
        ///	
        ///		...
        ///		context.LogInfo( "HTTP Response was successfully received" );
        ///	</code>
        ///	
        ///	</remarks>
        public void LogInfo(string text)
        {
            ArgumentValidation.CheckForNullReference(text, "text");

            this.logger.Log(LogLevel.INFO, text);
        }

        /// <summary>
        /// Used by a test step to log test information, this will be logged in the test output.
        /// </summary>
        /// <param name="text">The text to be written to the output.</param>
        /// <param name="args">Array of arguments to be formatted with the text.</param>
        /// 
        /// <remarks> 
        /// The following example demonstrates how to use the method:
        /// 
        /// <code escaped="true">
        ///	public void Execute(XmlNode testConfig, Context context)
        ///	{
        ///	
        ///		...
        ///		context.LogInfo( "HTTP Response was successfully received by: {0}, at: {1}", clientName, DateTime.Now );
        ///	</code>
        ///	
        ///	</remarks>
        public void LogInfo(string text, params object[] args)
        {
            ArgumentValidation.CheckForNullReference(text, "text");
            ArgumentValidation.CheckForNullReference(args, "args");

            this.logger.Log(LogLevel.INFO, string.Format(text, args));
        }

        /// <summary>
        /// Used by a test step to log a test warnings, this will be logged in the test output.
        /// </summary>
        /// <param name="text">The text to be written to the output.</param>
        /// 
        /// <remarks> 
        /// The following example demonstrates how to use the method:
        /// 
        /// <code escaped="true">
        ///	public void Execute(XmlNode testConfig, Context context)
        ///	{
        ///	
        ///		...
        ///		context.LogWarning( "The FILE was found, retrying..." );
        ///	</code>
        ///	
        ///	</remarks>
        public void LogWarning(string text)
        {
            ArgumentValidation.CheckForNullReference(text, "text");

            this.logger.Log(LogLevel.WARNING, text);
        }

        /// <summary>
        /// Used by a test step to log a test warnings, this will be logged in the test output.
        /// </summary>
        /// <param name="text">The text to be written to the output.</param>
        /// <param name="args">Array of arguments to be formatted with the text.</param>
        /// 
        /// <remarks> 
        /// The following example demonstrates how to use the method:
        /// 
        /// <code escaped="true">
        ///	public void Execute(XmlNode testConfig, Context context)
        ///	{
        ///	
        ///		...
        ///		context.LogWarning( "The FILE was found, poll nummber: {0}, number of retries remaining: {1}", count, retriesLeft );
        ///	</code>
        ///	
        ///	</remarks>
        public void LogWarning(string text, params object[] args)
        {
            ArgumentValidation.CheckForNullReference(text, "text");
            ArgumentValidation.CheckForNullReference(args, "args");

            this.logger.Log(LogLevel.WARNING, string.Format(text, args));
        }

        /// <summary>
        /// Used by a test step to log a test error, this will be logged in the test output.
        /// </summary>
        /// <param name="text">The text to be written to the output.</param>
        /// 
        /// <remarks> 
        /// The following example demonstrates how to use the method:
        /// 
        /// <code escaped="true">
        ///	public void Execute(XmlNode testConfig, Context context)
        ///	{
        ///	
        ///		...
        ///		context.LogError( "The response data was invalid." );
        ///	</code>
        ///	
        ///	</remarks>
        public void LogError(string text)
        {
            ArgumentValidation.CheckForNullReference(text, "text");

            this.logger.Log(LogLevel.ERROR, text);
        }

        /// <summary>
        /// Used by a test step to log a test error, this will be logged in the test output.
        /// </summary>
        /// <param name="text">The text to be written to the output.</param>
        /// <param name="args">Array of arguments to be formatted with the text.</param>
        /// 
        /// <remarks> 
        /// The following example demonstrates how to use the method:
        /// 
        /// <code escaped="true">
        ///	public void Execute(XmlNode testConfig, Context context)
        ///	{
        ///	
        ///		...
        ///		context.LogError( "The request failed with the folowing error: {0}", requestErrorText );
        ///	</code>
        ///	
        ///	</remarks>
        public void LogError(string text, params object[] args)
        {
            ArgumentValidation.CheckForNullReference(text, "text");
            ArgumentValidation.CheckForNullReference(args, "args");

            this.logger.LogError(text, args);
        }

        /// <summary>
        /// Used by a test step to get the time that the test case started.
        /// </summary>
        /// 
        /// <remarks> 
        /// The following example demonstrates how to use the method:
        /// 
        /// <code escaped="true">
        ///	public void Execute(XmlNode testConfig, Context context)
        ///	{
        ///	
        ///		...
        ///		DateTime testStart = context.TestCaseStart;
        ///	</code>
        ///	
        ///	</remarks>
        public DateTime TestCaseStart
        {
            get
            {
                return this.startTime;
            }
        }

        /// <summary>
        /// Used to substitute wild cards into strings.
        /// </summary>
        /// 
        /// <remarks> 
        /// The following wild cards are suported:
        /// 
        /// <code escaped="true">
        /// %DateTime% - will replace the wild card with the current date time in the format HHmmss-ddMMyyyy
        /// %ServerName% - will replace the wild card with the name of the server BizUnit is being executed on
        /// %Guid% - will be replaced by a new Guid
        ///	</code>
        ///	
        ///	</remarks>
        public string SubstituteWildCards(string rawString)
        {
            ArgumentValidation.CheckForNullReference(rawString, "rawString");

            string result = rawString;

            if (result.Contains(DATE_TIME))
            {
                result = result.Replace(DATE_TIME, DateTime.Now.ToString("HHmmss-ddMMyyyy"));
            }

            if (result.Contains(SERVER_NAME))
            {
                result = result.Replace(SERVER_NAME, Environment.MachineName);
            }

            if (result.Contains(GUID))
            {
                result = result.Replace(GUID, Guid.NewGuid().ToString());
            }

            return result;
        }
    }
}
